#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
CSV文件处理器

该模块提供了CSV文件的读写和处理功能。

创建时间: 2023-07-12
作者: Python Tools Team
"""

import csv
import io
from typing import Optional, Union, Dict, Any, List, Tuple, Iterator, Callable
from pathlib import Path

from modules.logger import LogManager
from modules.exception import exception_handler, FileException
from modules.fileio.file_manager import FileManager


class CSVFileHandler:
    """
    CSV文件处理器
    
    该类提供了CSV文件的读写和处理功能，支持多种格式和选项。
    """
    
    def __init__(self, file_manager: Optional[FileManager] = None, logger=None):
        """
        初始化CSV文件处理器
        
        Args:
            file_manager: 文件管理器实例，如果为None则创建新实例
            logger: 日志记录器，如果为None则使用默认日志记录器
        """
        self.file_manager = file_manager or FileManager()
        self.logger = logger or LogManager.get_both_logger(name="csv_handler")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read(self, file_path: Union[str, Path], delimiter: str = ',', quotechar: str = '"', 
             encoding: str = 'utf-8', has_header: bool = True) -> List[Dict[str, str]]:
        """
        读取CSV文件为字典列表
        
        Args:
            file_path: CSV文件路径
            delimiter: 字段分隔符，默认为逗号
            quotechar: 引号字符，默认为双引号
            encoding: 文件编码，默认为utf-8
            has_header: 是否有标题行，默认为True
        
        Returns:
            List[Dict[str, str]]: 字典列表，每个字典代表一行数据，键为列名
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> csv_handler = CSVFileHandler()
            >>> data = csv_handler.read('example.csv')
            >>> for row in data:
            >>>     print(row)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        content = self.file_manager.read_text(file_path, encoding=encoding)
        
        result = []
        reader = csv.reader(io.StringIO(content), delimiter=delimiter, quotechar=quotechar)
        
        # 读取标题行
        headers = next(reader) if has_header else None
        
        # 如果没有标题行，使用列索引作为键
        if not has_header:
            for i, row in enumerate(reader):
                row_dict = {f"column_{j}": value for j, value in enumerate(row)}
                result.append(row_dict)
        else:
            for i, row in enumerate(reader):
                # 确保行长度与标题行一致
                if len(row) < len(headers):
                    row.extend([''] * (len(headers) - len(row)))
                elif len(row) > len(headers):
                    row = row[:len(headers)]
                
                row_dict = {headers[j]: value for j, value in enumerate(row)}
                result.append(row_dict)
        
        self.logger.debug(f"已读取CSV文件: {file_path}，共{len(result)}行")
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read_as_list(self, file_path: Union[str, Path], delimiter: str = ',', quotechar: str = '"', 
                    encoding: str = 'utf-8', skip_header: bool = False) -> List[List[str]]:
        """
        读取CSV文件为列表的列表
        
        Args:
            file_path: CSV文件路径
            delimiter: 字段分隔符，默认为逗号
            quotechar: 引号字符，默认为双引号
            encoding: 文件编码，默认为utf-8
            skip_header: 是否跳过标题行，默认为False
        
        Returns:
            List[List[str]]: 列表的列表，每个内部列表代表一行数据
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> csv_handler = CSVFileHandler()
            >>> data = csv_handler.read_as_list('example.csv')
            >>> for row in data:
            >>>     print(row)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        content = self.file_manager.read_text(file_path, encoding=encoding)
        
        reader = csv.reader(io.StringIO(content), delimiter=delimiter, quotechar=quotechar)
        
        # 跳过标题行
        if skip_header:
            next(reader)
        
        result = [row for row in reader]
        
        self.logger.debug(f"已读取CSV文件: {file_path}，共{len(result)}行")
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write(self, file_path: Union[str, Path], data: List[Dict[str, Any]], 
              fieldnames: Optional[List[str]] = None, delimiter: str = ',', 
              quotechar: str = '"', quoting: int = csv.QUOTE_MINIMAL, 
              encoding: str = 'utf-8', create_dirs: bool = True) -> bool:
        """
        将字典列表写入CSV文件
        
        Args:
            file_path: CSV文件路径
            data: 要写入的数据，字典列表
            fieldnames: 字段名列表，如果为None则使用第一个字典的所有键
            delimiter: 字段分隔符，默认为逗号
            quotechar: 引号字符，默认为双引号
            quoting: 引号策略，默认为csv.QUOTE_MINIMAL
            encoding: 文件编码，默认为utf-8
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
            ValueError: 数据为空时抛出
        
        Examples:
            >>> csv_handler = CSVFileHandler()
            >>> data = [
            >>>     {'name': 'Alice', 'age': 30},
            >>>     {'name': 'Bob', 'age': 25}
            >>> ]
            >>> csv_handler.write('example.csv', data)
        """
        file_path = Path(file_path)
        
        if not data:
            raise ValueError("数据不能为空")
        
        # 如果未指定字段名，使用第一个字典的所有键
        if fieldnames is None:
            fieldnames = list(data[0].keys())
        
        # 如果需要，创建父目录
        if create_dirs:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        
        with open(file_path, 'w', newline='', encoding=encoding) as f:
            writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=delimiter, 
                                   quotechar=quotechar, quoting=quoting)
            writer.writeheader()
            for row in data:
                # 确保只写入指定的字段
                filtered_row = {k: row.get(k, '') for k in fieldnames}
                writer.writerow(filtered_row)
        
        self.logger.debug(f"已写入CSV文件: {file_path}，共{len(data)}行")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write_rows(self, file_path: Union[str, Path], data: List[List[Any]], 
                   header: Optional[List[str]] = None, delimiter: str = ',', 
                   quotechar: str = '"', quoting: int = csv.QUOTE_MINIMAL, 
                   encoding: str = 'utf-8', create_dirs: bool = True) -> bool:
        """
        将行列表写入CSV文件
        
        Args:
            file_path: CSV文件路径
            data: 要写入的数据，行列表
            header: 标题行，如果为None则不写入标题
            delimiter: 字段分隔符，默认为逗号
            quotechar: 引号字符，默认为双引号
            quoting: 引号策略，默认为csv.QUOTE_MINIMAL
            encoding: 文件编码，默认为utf-8
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> csv_handler = CSVFileHandler()
            >>> data = [
            >>>     ['Alice', 30],
            >>>     ['Bob', 25]
            >>> ]
            >>> header = ['Name', 'Age']
            >>> csv_handler.write_rows('example.csv', data, header=header)
        """
        file_path = Path(file_path)
        
        # 如果需要，创建父目录
        if create_dirs:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        
        with open(file_path, 'w', newline='', encoding=encoding) as f:
            writer = csv.writer(f, delimiter=delimiter, quotechar=quotechar, quoting=quoting)
            
            # 写入标题行
            if header is not None:
                writer.writerow(header)
            
            # 写入数据行
            writer.writerows(data)
        
        self.logger.debug(f"已写入CSV文件: {file_path}，共{len(data)}行")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def append(self, file_path: Union[str, Path], data: List[Dict[str, Any]], 
               fieldnames: Optional[List[str]] = None, delimiter: str = ',', 
               quotechar: str = '"', quoting: int = csv.QUOTE_MINIMAL, 
               encoding: str = 'utf-8', create_if_not_exists: bool = True, 
               create_dirs: bool = True) -> bool:
        """
        将字典列表追加到CSV文件
        
        Args:
            file_path: CSV文件路径
            data: 要追加的数据，字典列表
            fieldnames: 字段名列表，如果为None则尝试从文件中读取或使用第一个字典的所有键
            delimiter: 字段分隔符，默认为逗号
            quotechar: 引号字符，默认为双引号
            quoting: 引号策略，默认为csv.QUOTE_MINIMAL
            encoding: 文件编码，默认为utf-8
            create_if_not_exists: 如果文件不存在，是否创建，默认为True
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 追加成功返回True
        
        Raises:
            FileException: 追加失败时抛出
            ValueError: 数据为空时抛出
        
        Examples:
            >>> csv_handler = CSVFileHandler()
            >>> data = [
            >>>     {'name': 'Charlie', 'age': 35},
            >>>     {'name': 'David', 'age': 40}
            >>> ]
            >>> csv_handler.append('example.csv', data)
        """
        file_path = Path(file_path)
        
        if not data:
            raise ValueError("数据不能为空")
        
        # 如果文件不存在且允许创建
        if not file_path.exists():
            if create_if_not_exists:
                return self.write(file_path, data, fieldnames, delimiter, quotechar, 
                                 quoting, encoding, create_dirs)
            else:
                raise FileException(f"文件不存在: {file_path}")
        
        # 如果未指定字段名，尝试从文件中读取
        if fieldnames is None:
            try:
                with open(file_path, 'r', newline='', encoding=encoding) as f:
                    reader = csv.reader(f, delimiter=delimiter, quotechar=quotechar)
                    fieldnames = next(reader)  # 读取标题行
            except Exception:
                # 如果读取失败，使用第一个字典的所有键
                fieldnames = list(data[0].keys())
        
        # 追加数据
        with open(file_path, 'a', newline='', encoding=encoding) as f:
            writer = csv.DictWriter(f, fieldnames=fieldnames, delimiter=delimiter, 
                                   quotechar=quotechar, quoting=quoting)
            for row in data:
                # 确保只写入指定的字段
                filtered_row = {k: row.get(k, '') for k in fieldnames}
                writer.writerow(filtered_row)
        
        self.logger.debug(f"已追加到CSV文件: {file_path}，共{len(data)}行")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def append_rows(self, file_path: Union[str, Path], data: List[List[Any]], 
                    delimiter: str = ',', quotechar: str = '"', 
                    quoting: int = csv.QUOTE_MINIMAL, encoding: str = 'utf-8', 
                    create_if_not_exists: bool = True, header: Optional[List[str]] = None, 
                    create_dirs: bool = True) -> bool:
        """
        将行列表追加到CSV文件
        
        Args:
            file_path: CSV文件路径
            data: 要追加的数据，行列表
            delimiter: 字段分隔符，默认为逗号
            quotechar: 引号字符，默认为双引号
            quoting: 引号策略，默认为csv.QUOTE_MINIMAL
            encoding: 文件编码，默认为utf-8
            create_if_not_exists: 如果文件不存在，是否创建，默认为True
            header: 标题行，仅在创建新文件时使用
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 追加成功返回True
        
        Raises:
            FileException: 追加失败时抛出
        
        Examples:
            >>> csv_handler = CSVFileHandler()
            >>> data = [
            >>>     ['Charlie', 35],
            >>>     ['David', 40]
            >>> ]
            >>> csv_handler.append_rows('example.csv', data)
        """
        file_path = Path(file_path)
        
        # 如果文件不存在且允许创建
        if not file_path.exists():
            if create_if_not_exists:
                return self.write_rows(file_path, data, header, delimiter, quotechar, 
                                      quoting, encoding, create_dirs)
            else:
                raise FileException(f"文件不存在: {file_path}")
        
        # 如果需要，创建父目录
        if create_dirs:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        
        # 追加数据
        with open(file_path, 'a', newline='', encoding=encoding) as f:
            writer = csv.writer(f, delimiter=delimiter, quotechar=quotechar, quoting=quoting)
            writer.writerows(data)
        
        self.logger.debug(f"已追加到CSV文件: {file_path}，共{len(data)}行")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def convert_to_dict(self, rows: List[List[Any]], header: Optional[List[str]] = None) -> List[Dict[str, Any]]:
        """
        将行列表转换为字典列表
        
        Args:
            rows: 行列表
            header: 标题行，如果为None则使用第一行作为标题
        
        Returns:
            List[Dict[str, Any]]: 字典列表
        
        Raises:
            ValueError: 行列表为空时抛出
        
        Examples:
            >>> csv_handler = CSVFileHandler()
            >>> rows = [
            >>>     ['Name', 'Age'],
            >>>     ['Alice', 30],
            >>>     ['Bob', 25]
            >>> ]
            >>> data = csv_handler.convert_to_dict(rows)
            >>> print(data)
            >>> # 输出: [{'Name': 'Alice', 'Age': 30}, {'Name': 'Bob', 'Age': 25}]
        """
        if not rows:
            raise ValueError("行列表不能为空")
        
        if header is None:
            header = rows[0]
            data_rows = rows[1:]
        else:
            data_rows = rows
        
        result = []
        for row in data_rows:
            # 确保行长度与标题行一致
            if len(row) < len(header):
                row = list(row) + [''] * (len(header) - len(row))
            elif len(row) > len(header):
                row = row[:len(header)]
            
            row_dict = {header[i]: value for i, value in enumerate(row)}
            result.append(row_dict)
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def convert_to_rows(self, dicts: List[Dict[str, Any]], fieldnames: Optional[List[str]] = None) -> List[List[Any]]:
        """
        将字典列表转换为行列表
        
        Args:
            dicts: 字典列表
            fieldnames: 字段名列表，如果为None则使用第一个字典的所有键
        
        Returns:
            List[List[Any]]: 行列表，包括标题行
        
        Raises:
            ValueError: 字典列表为空时抛出
        
        Examples:
            >>> csv_handler = CSVFileHandler()
            >>> dicts = [
            >>>     {'Name': 'Alice', 'Age': 30},
            >>>     {'Name': 'Bob', 'Age': 25}
            >>> ]
            >>> rows = csv_handler.convert_to_rows(dicts)
            >>> print(rows)
            >>> # 输出: [['Name', 'Age'], ['Alice', 30], ['Bob', 25]]
        """
        if not dicts:
            raise ValueError("字典列表不能为空")
        
        if fieldnames is None:
            fieldnames = list(dicts[0].keys())
        
        result = [fieldnames]  # 标题行
        
        for d in dicts:
            row = [d.get(field, '') for field in fieldnames]
            result.append(row)
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def filter_rows(self, data: List[Dict[str, Any]], condition: Callable[[Dict[str, Any]], bool]) -> List[Dict[str, Any]]:
        """
        根据条件筛选行
        
        Args:
            data: 字典列表
            condition: 筛选条件函数，接受一个字典参数，返回布尔值
        
        Returns:
            List[Dict[str, Any]]: 筛选后的字典列表
        
        Examples:
            >>> csv_handler = CSVFileHandler()
            >>> data = [
            >>>     {'name': 'Alice', 'age': 30},
            >>>     {'name': 'Bob', 'age': 25},
            >>>     {'name': 'Charlie', 'age': 35}
            >>> ]
            >>> # 筛选年龄大于等于30的行
            >>> filtered = csv_handler.filter_rows(data, lambda row: row['age'] >= 30)
            >>> print(filtered)
            >>> # 输出: [{'name': 'Alice', 'age': 30}, {'name': 'Charlie', 'age': 35}]
        """
        return [row for row in data if condition(row)]
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def sort_rows(self, data: List[Dict[str, Any]], key: str, reverse: bool = False) -> List[Dict[str, Any]]:
        """
        根据指定键对行进行排序
        
        Args:
            data: 字典列表
            key: 排序键
            reverse: 是否降序排序，默认为False（升序）
        
        Returns:
            List[Dict[str, Any]]: 排序后的字典列表
        
        Examples:
            >>> csv_handler = CSVFileHandler()
            >>> data = [
            >>>     {'name': 'Alice', 'age': 30},
            >>>     {'name': 'Bob', 'age': 25},
            >>>     {'name': 'Charlie', 'age': 35}
            >>> ]
            >>> # 按年龄排序
            >>> sorted_data = csv_handler.sort_rows(data, 'age')
            >>> print(sorted_data)
            >>> # 输出: [{'name': 'Bob', 'age': 25}, {'name': 'Alice', 'age': 30}, {'name': 'Charlie', 'age': 35}]
        """
        return sorted(data, key=lambda x: x.get(key), reverse=reverse)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def group_by(self, data: List[Dict[str, Any]], key: str) -> Dict[Any, List[Dict[str, Any]]]:
        """
        根据指定键对行进行分组
        
        Args:
            data: 字典列表
            key: 分组键
        
        Returns:
            Dict[Any, List[Dict[str, Any]]]: 分组结果，键为分组值，值为该组的行列表
        
        Examples:
            >>> csv_handler = CSVFileHandler()
            >>> data = [
            >>>     {'name': 'Alice', 'department': 'HR', 'age': 30},
            >>>     {'name': 'Bob', 'department': 'IT', 'age': 25},
            >>>     {'name': 'Charlie', 'department': 'HR', 'age': 35}
            >>> ]
            >>> # 按部门分组
            >>> grouped = csv_handler.group_by(data, 'department')
            >>> print(grouped['HR'])
            >>> # 输出: [{'name': 'Alice', 'department': 'HR', 'age': 30}, {'name': 'Charlie', 'department': 'HR', 'age': 35}]
        """
        result = {}
        for row in data:
            group_value = row.get(key)
            if group_value not in result:
                result[group_value] = []
            result[group_value].append(row)
        return result